﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using SevenZip;
using System.IO;
using System.Drawing.Imaging;

namespace Screener.Imaging
{
    public class Buffer
    {
        public static Buffer FromFile(string fileName)
        {
            Buffer buffer = new Buffer();

            System.Runtime.Serialization.Formatters.Binary.BinaryFormatter bf = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();

            if (File.Exists("7z.dll"))
            {
                SevenZipCompressor.SetLibraryPath(Path.GetFullPath("7z.dll"));
            }

            using (FileStream fs = File.OpenRead(fileName))
            {
                using (MemoryStream ms = new MemoryStream())
                {
                    SevenZipExtractor extractor = new SevenZipExtractor(fs);

                    extractor.ExtractFile(0, ms);

                    ms.Position = 0;

                    buffer.Read(ms);
                }
            }

            buffer.Cells.Sort((x, y) => Cell.Compare(x, y));

            return buffer;
        }

        private int[,] lastFountAt;

        public Buffer()
        {
            this.Cells = new List<Cell>();
        }

        public Size Size { get; set; }

        public Size ScreenSize { get; set; }

        public List<Cell> Cells;

        public short Index { get; set; }

        public Buffer(Size size, Size screen)
        {
            this.Size = size;
            this.ScreenSize = screen;

            this.Cells = new List<Cell>();
        }

        public void Init()
        {
            lastFountAt = new int[this.ScreenSize.Width / this.Size.Width, this.ScreenSize.Height / this.Size.Height];
        }

        public void Add(Bitmap bmp)
        {
            bool found = false;
            for (int j = 0; j < this.ScreenSize.Height / this.Size.Height; j++)
            {
                for (int i = 0; i < this.ScreenSize.Width / this.Size.Width; i++)
                {
                    Cell c = new Cell(bmp, i, j, this.Size);
                    c.CellInfo.Add(new CellInfo(this.Index, i, j));

                    found = false;

                    foreach (var c2 in this.Cells)
                    {
                        if (Compare(c, c2))
                        {
                            c2.AddCellinfo(this.Index, i, j);
                            found = true;
                            break;
                        }
                    }

                    if (!found)
                    {
                        this.Cells.Add(c);
                    }
                }
            }

            this.Index++;
        }

        private bool Compare(Cell c1, Cell c2)
        {
            for (int i = 0; i < c1.Hash.Length; i++)
            {
                if (c1.Hash[i] != c2.Hash[i])
                    return false;
            }

            return true;
        }

        private Cell GetCell(short index, short i, short j)
        {
            for (int x = lastFountAt[i, j]; x < this.Cells.Count; x++)
            {
                foreach (CellInfo item in this.Cells[x].CellInfo)
                {
                    if (item.Indexes.Contains(index) && item.X == i && item.Y == j)
                    {
                        lastFountAt[i, j] = x;

                        return this.Cells[x];
                    }
                }
            }

            for (int x = 0; x < lastFountAt[i, j]; x++)
            {
                foreach (CellInfo item in this.Cells[x].CellInfo)
                {
                    if (item.Indexes.Contains(index) && item.X == i && item.Y == j)
                    {
                        lastFountAt[i, j] = x;

                        return this.Cells[x];
                    }
                }
            }

            return this.Cells[this.Cells.Count - 1];
        }

        public Bitmap GetImage(short index)
        {
            Bitmap bmp = new Bitmap(this.ScreenSize.Width, this.ScreenSize.Height);

            unsafe
            {
                BitmapData bData = bmp.LockBits(new Rectangle(0, 0, this.ScreenSize.Width, this.ScreenSize.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
                byte* scan0 = (byte*)bData.Scan0.ToPointer();

                for (short i = 0; i < this.ScreenSize.Width / this.Size.Width; i++)
                {
                    for (short j = 0; j < this.ScreenSize.Height / this.Size.Height; j++)
                    {
                        Cell c = GetCell(index, i, j);

                        for (int x = 0; x < this.Size.Width; x++)
                        {
                            for (int y = 0; y < this.Size.Height; y++)
                            {
                                //todo: Move this to own method
                                byte* data = scan0 + (j * this.Size.Width + y) * bData.Stride + (i * this.Size.Height + x) * 24 / 8;

                                switch ((x + y) % 3)
                                {
                                    case 0:
                                        data[2] = (byte)((((c.Data[x * this.Size.Height + y] & 0xC0) >> 6) + 1) * 64 - 1);
                                        data[1] = (byte)((((c.Data[x * this.Size.Height + y] & 0x38) >> 3) + 1) * 32 - 1);
                                        data[0] = (byte)(((c.Data[x * this.Size.Height + y] & 0x7) + 1) * 32 - 1);
                                        break;
                                    case 1:
                                        data[0] = (byte)((((c.Data[x * this.Size.Height + y] & 0xC0) >> 6) + 1) * 64 - 1);
                                        data[2] = (byte)((((c.Data[x * this.Size.Height + y] & 0x38) >> 3) + 1) * 32 - 1);
                                        data[1] = (byte)(((c.Data[x * this.Size.Height + y] & 0x7) + 1) * 32 - 1);
                                        break;
                                    case 2:
                                        data[1] = (byte)((((c.Data[x * this.Size.Height + y] & 0xC0) >> 6) + 1) * 64 - 1);
                                        data[0] = (byte)((((c.Data[x * this.Size.Height + y] & 0x38) >> 3) + 1) * 32 - 1);
                                        data[2] = (byte)(((c.Data[x * this.Size.Height + y] & 0x7) + 1) * 32 - 1);
                                        break;
                                }
                            }
                        }
                    }
                }

                bmp.UnlockBits(bData);
            }

            return bmp;
        }

        public void Write(Stream stream)
        {
            BinaryWriter bw = new BinaryWriter(stream);
            bw.Write(this.Size.Width);
            bw.Write(this.Size.Height);
            bw.Write(this.ScreenSize.Width);
            bw.Write(this.ScreenSize.Height);
            bw.Write(this.Index);
            bw.Write(this.Cells.Count);

            this.Cells[0].CellInfo = new List<CellInfo>();

            for (int i = 0; i < this.Cells.Count; i++)
            {
                this.Cells[i].Write(bw);
            }
        }

        public void Read(Stream stream)
        {
            using (BinaryReader br = new BinaryReader(stream))
            {
                int width, height;

                width = br.ReadInt32();
                height = br.ReadInt32();
                this.Size = new Size(width, height);


                width = br.ReadInt32();
                height = br.ReadInt32();
                this.ScreenSize = new Size(width, height);

                this.Index = br.ReadInt16();

                Init();

                int cCount = br.ReadInt32();

                Cell cell;

                for (int i = 0; i < cCount; i++)
                {

                    cell = new Cell();
                    cell.Size = this.Size;
                    cell.Read(br);

                    this.Cells.Add(cell);
                }
            }
        }
    }
}
